package io.hellsinger.filesystem.linux.operation

import io.hellsinger.filesystem.linux.LinuxFileOperationProvider
import io.hellsinger.filesystem.linux.LinuxOperationOptions.Open
import io.hellsinger.filesystem.linux.attribute.attributes
import io.hellsinger.filesystem.linux.file.LinuxFile
import io.hellsinger.filesystem.path.Path
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

suspend fun <P : Path> LinuxFile<P>.sendFile(
    dest: P,
    count: Int = path.attributes.size.toInt(),
    offset: Long? = null,
    onByteCount: suspend (Int) -> Unit,
) {
    val sourceMode = LinuxFileOperationProvider.getStatus(path.bytes)?.mode ?: return
    val destMode = LinuxFileOperationProvider.getStatus(dest.bytes)?.mode ?: sourceMode

    val fd =
        LinuxFileOperationProvider.openFileDescriptor(
            path = path.bytes,
            flags = Open.NON_BLOCKING or Open.READ_ONLY,
            mode = sourceMode,
        )
    val destFd =
        LinuxFileOperationProvider.openFileDescriptor(
            path = dest.bytes,
            flags = Open.NON_BLOCKING or Open.WRITE_ONLY or Open.CREATE_NEW,
            mode = destMode,
        )

    while (true) {
        val bytes = send(fd, destFd, offset, count)
        if (bytes == 0) break
        onByteCount(bytes)
    }

    LinuxFileOperationProvider.closeFileDescriptor(destFd)
    LinuxFileOperationProvider.closeFileDescriptor(fd)
}

fun <P : Path> LinuxFile<P>.sendFileFlow(
    dest: P,
    count: Int = LinuxFileOperationProvider.getStatus(path.bytes)!!.size.toInt(),
    offset: Long? = null,
): Flow<Int> =
    flow {
        sendFile(dest = dest, count = count, offset = offset, onByteCount = ::emit)
    }

private suspend fun send(
    fd: Int,
    destFD: Int,
    offset: Long?,
    count: Int,
): Int =
    suspendCoroutine { continuation ->
        continuation.resume(
            LinuxFileOperationProvider.sendBytes(
                fd,
                destFD,
                offset,
                count,
            ),
        )
    }
